package com.basicorm;

import com.basicorm.annotations.Column;
import com.basicorm.annotations.Table;
import com.basicorm.db.DbTestUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;

public class EntityStorageImplExTest {
    private static final String TABLE_NAME = "orm_test_table";

    private EntityStorageImplEx<TestEntity> storage;
    private Connection connection;
    private DbTestUtil dbTestUtil;

    @Before
    public void setUp() throws Exception {
        connection = DbTestUtil.getConnection();
        storage = new EntityStorageImplEx<TestEntity>(connection, TestEntity.class);
        dbTestUtil = new DbTestUtil(connection);
        dbTestUtil.createTable("create table orm_test_table (cId int, cName varchar(10))");
    }

    @Test
    public void testInsert() throws Exception {
    	TestEntity entity = new TestEntity(); 
    	entity.id = 10;
    	entity.name = "peace";
        storage.insert(entity);
        assertEquals(1, dbTestUtil.countFromTable(TABLE_NAME));
        assertEquals("10, peace", dbTestUtil.select("select * from " + TABLE_NAME ));
    }
    
    @Test
    public void testGetTableInf() throws Exception {
    	TestEntity entity = new TestEntity(); 
    	entity.id = 10;
    	entity.name = "peace";
    	assertEquals(MyMap.create("cName", (Object)"peace").add("cId", 10), storage.getTableInf(entity));
    }
    
    @Test
    public void testFindAll_emptyTable() throws SecurityException, Throwable {
    	assertEquals(true, storage.findAll().isEmpty());
    }
    
    @Test
    public void testFindAll_notEmptyTable() throws SecurityException, Throwable {
    	TestEntity entity = new TestEntity();
    	entity.id = 1;
    	entity.name = "name";
    	connection.createStatement().executeUpdate("insert into " + TABLE_NAME + " values("+ entity.id +", '"+ entity.name +"')");
    	assertEquals(Arrays.asList(entity), storage.findAll());
    }
    
	@Test
	public void testSelectValuesConditionFromMap() throws Throwable {
    	TestEntity entity = new TestEntity(); 
    	entity.id = 10;
    	entity.name = "peace";
    	connection.createStatement().executeUpdate("insert into " + TABLE_NAME + " values('"+ entity.id +"', '"+ entity.name +"')");
    	Map<String, Object> map = storage.getTableInf(entity);
    	List<String> columns = storage.getColumns();
    	assertEquals(Arrays.asList(MyMap.create("cName", (Object)"peace").add("cId", 10)), storage.selectValuesConditionFromMap(TABLE_NAME, columns, map));
	}
    
    @Test
    public void testfindByExample_nullCondition() throws Throwable {
    	TestEntity entity = new TestEntity(); 
    	entity.id = 1;
    	entity.name = "name";
    	connection.createStatement().executeUpdate("insert into " + TABLE_NAME + " values('"+ entity.id +"', '"+ entity.name +"')");
    	assertEquals(Arrays.asList(entity), storage.findByExample(null));
    }
    
    @Test
    public void testfindByExample_notNullCondition() throws Throwable {
    	connection.createStatement().executeUpdate("insert into " + TABLE_NAME + " values(1, 'name')");
    	assertEquals(Arrays.asList(new TestEntity(1, "name")), storage.findByExample(new TestEntity(1, null)));
    }
    
    @Test
    public void testFindByExample_conditionWithSingleQuotes() throws Throwable {
    	connection.createStatement().executeUpdate("insert into " + TABLE_NAME + " values(1, 'o''Brian')");
    	assertEquals(Arrays.asList(new TestEntity(1, "o'Brian")), storage.findByExample(new TestEntity(null, "o'Brian")));
    }

	@Test
	public void testSelectValues() throws Exception {
		List<Map<String, Object>> listMap =  Arrays.<Map<String, Object>>asList(MyMap.create("cId", (Object)1).add("cName", "name"), MyMap.create("cId", (Object)3).add("cName", "name1"));
		//List<Map<String, String>> listMap =  Arrays.asList(asMap(asMapString("cId", "1"), asMapString("cName", "name")), asMap(asMapString("cId", "3"), asMapString("cName", "name1")));
		connection.createStatement().executeUpdate("insert into " + TABLE_NAME + " values(1, 'name'), (3, 'name1')");
    	assertEquals(listMap, storage.selectValues(TABLE_NAME, Arrays.asList("cId", "cName")));
	}

	private Map<String, String> asMap(Map<String, String>... asMapString) {
		Map<String, String> map = new HashMap<String, String>();
		List<Map<String, String>> listMap = Arrays.asList(asMapString);
		for (Map<String, String> m: listMap){
			map.putAll(m);
		}
		return map;
	}

	private Map<String, String> asMapString(String string, String string2) {
    	Map<String, String> map = new HashMap<String, String>(); 
		map.put(string, string2);
		return map;
	}
    
	@Test
	public void testCopyValuesToObject() throws Exception {
		TestEntity expected = new TestEntity();
		expected.id = 1;
		TestEntity actual = new TestEntity();
		Map<String, Object> mapColumn = new HashMap<String, Object>();
		mapColumn.put("id", 1);
		Map<String, Field> mapField = new HashMap<String, Field>();
		Field f = actual.getClass().getDeclaredField("id");
		f.setAccessible(true);
		mapField.put("id", f);
		storage.copyValuesToObject(mapColumn, mapField, actual);
		assertEquals(expected, actual);
	}
	
	@Test
	public void testGetColumnsFieldInf() throws Exception {
		Map<String, Field> expected = new HashMap<String, Field>();
		expected.put("cName", TestEntity.class.getDeclaredField("name"));
		expected.put("cId", TestEntity.class.getDeclaredField("id"));
		assertEquals(expected, storage.getColumnsFieldInf(TestEntity.class));
	}

	@After 
    public void tearDown() throws Exception {
    	dbTestUtil.dropTable(TABLE_NAME);
        connection.close();
    }

    /*
        TODO VM Specify table name to be TABLE_NAME.
         */
    @Table(TABLE_NAME)
    public static class TestEntity {
    	@Column("cId")
    	private Integer id;
    	
    	@Column("cName")
    	private String name;
    	
    	public TestEntity() {
		}
    	
    	public TestEntity(Integer id, String name){
    		this.id = id;
    		this.name = name;
    	}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((id == null) ? 0 : id.hashCode());
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			TestEntity other = (TestEntity) obj;
			if (id == null) {
				if (other.id != null)
					return false;
			} else if (!id.equals(other.id))
				return false;
			if (name == null) {
				if (other.name != null)
					return false;
			} else if (!name.equals(other.name))
				return false;
			return true;
		}

		@Override
		public String toString() {
			return "TestEntity [id=" + id + ", name=" + name + "]";
		}



    }
    
    static class MyMap<Key, Value> extends HashMap<Key, Value> {
    	static <Key, Value> MyMap<Key, Value> create(Key key, Value value){
    		MyMap<Key, Value> map = new MyMap<Key, Value>();
    		map.put(key, value);
			return map;
    	}
    	
    	MyMap<Key, Value> add(Key key, Value value){
    		put(key, value);
			return this;
    	}
    }


}
